package evs.location;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;

import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.servlet.http.HttpServletResponse;

import org.hibernate.Session;

public class EntityDeserializer
{
    private final EntityMap _entityMap;
    private final boolean _allowModifyingExistingEntities;
    private final boolean _allowCreatingNewEntities;
    
    public EntityDeserializer( EntityMap entityMap, boolean allowModifyingExistingEntities, 
                    boolean allowCreatingNewEntities )
    {
        _entityMap = entityMap;
        _allowModifyingExistingEntities = allowModifyingExistingEntities;
        _allowCreatingNewEntities = allowCreatingNewEntities;
    }
    
    public Object createEntity( EntityDom dom, Session session, boolean allowExisting ) 
        throws EvsException
    {
        boolean existing = _entityMap.hasIdElement( dom );
        
        if ( !allowExisting && existing )
            throw new EvsException( HttpServletResponse.SC_BAD_REQUEST,
                            "Entity must not have id element in POST request." );

        Class<?> entityClass = _entityMap.getClassFor( dom.name );
        
        Object entity;
        
        if ( existing )
        {
            if ( !_allowModifyingExistingEntities && dom.children.size() > 1 )
                throw new EvsException( HttpServletResponse.SC_BAD_REQUEST, 
                                "Cannot modify existing entities in this request!" );
            
            Serializable id = _entityMap.getId( dom );
            entity = session.get( entityClass, id );
        }
        else
        {
            if ( !_allowCreatingNewEntities )
                throw new EvsException( HttpServletResponse.SC_BAD_REQUEST, 
                    "Cannot create new entities in this request!" );
            
            try
            {
                entity = entityClass.newInstance();
            }
            catch ( Exception e )
            {
                throw new EvsException( e );
            }
        }
        
        for ( EntityDom child : dom.children )
        {
            try
            {
                String fieldName = child.name;
            
                Field field = EntityMap.getField( entityClass, fieldName );
                
                if ( field == null )
                    throw new EvsException( "Encountered unknown entity field: " + fieldName );
                
                field.setAccessible( true );
                
                if ( Collection.class.isAssignableFrom( field.getType() ) )
                {
                    Collection<?> collection = _createCollection( child, session, field );
                    
                    field.set( entity, collection );
                }
                else if ( field.getType().isAnnotationPresent( Entity.class ) )
                {
                    Object value = createEntity( child.children.get( 0 ), session, true );
                    field.set( entity, value );
                }
                else
                {
                    Object value = Converter.convertTo( child.value, field.getType() );
                    field.set( entity, value );
                }
            }
            catch ( Exception e )
            {
                throw new EvsException( e );
            }
        }
        
        if ( !existing )
        {
            entity = session.get( entityClass, session.save( entity ) );
        }
        else
        {
            session.update( entity );
        }
        
        return entity;
    }

    private Collection<?> _createCollection( EntityDom collectionDom, Session session, Field field ) 
        throws EvsException
    {
        @SuppressWarnings( "unchecked" )
        Collection<Object> collection = (Collection<Object>) Converter.newCollection( field.getType() );
        
        ParameterizedType fieldType = (ParameterizedType) field.getGenericType();
        Class<?> componentType = (Class<?>) fieldType.getActualTypeArguments()[0];
        
        if ( field.isAnnotationPresent( ElementCollection.class ) )
        {
            if ( componentType.isAnnotationPresent( Embeddable.class ) )
                throw new EvsException( "Collections of embeddedable classes not supported yet!" );
            
            for ( EntityDom itemDom : collectionDom.children )
            {
                collection.add( Converter.convertTo( itemDom.value, componentType ) );
            }
        }
        else
        {
            for ( EntityDom itemDom : collectionDom.children )
            {
                collection.add( createEntity( itemDom, session, true ) );
            }
        }
        
        return collection;
    }


}
